(function () {
  var Snake = function (opt) {
      this.mountedEl = opt.mountedEl;
      this.keyboardSetting = opt.keyboardSetting || 'A';
      this.difficulty = setDifficulty(opt.difficulty) || 500;

      //初始蛇的长度的数组坐标
      this.bodyArr = [
        { x: 0, y: 0 },
        { x: 0, y: 20 },
        { x: 0, y: 40 },
        { x: 0, y: 60 },
        { x: 0, y: 80 },
        { x: 0, y: 100 } //头
      ];
      this.dir = 'DOWN';
    },
    t = null,
    isBegin = false;

  Snake.prototype.getOElements = function () {
    var oStartBtn = this.mountedEl.getElementsByClassName('start')[0],
      oResetBtn = this.mountedEl.getElementsByClassName('reset')[0],
      oResult = this.mountedEl.getElementsByClassName('result')[0];

    return { oStartBtn, oResetBtn, oResult };
  };

  Snake.prototype.init = function () {
    this.firstRender();
    this.bindEvent(this.getOElements());
  };

  Snake.prototype.firstRender = function () {
    this.mountedEl.innerHTML = createSnakeElement();
  };

  //初始化一条蛇并插入节点
  Snake.prototype.initSnake = function () {
    var arr = this.bodyArr,
      len = arr.length,
      item,
      frag = document.createDocumentFragment(),
      oSnakeWrap = this.mountedEl.getElementsByClassName('snake-wrap')[0];

    for (var i = 0; i < len; i++) {
      item = arr[i]; //{ x: 0, y: 100 }
      var round = document.createElement('i');
      round.className = i === len - 1 ? 'round head' : 'round';
      round.style.left = item.x + 'px';
      round.style.top = item.y + 'px';
      frag.appendChild(round);
    }

    oSnakeWrap.appendChild(frag);
  };

  //定时移动使得蛇可以跑起来(自动执行)
  Snake.prototype.run = function () {
    var _self = this,
      speed = _self.difficulty;

    if (t === null) {
      t = setInterval(function () {
        _self.move();
      }, speed);
    }
  };

  Snake.prototype.move = function () {
    var arr = this.bodyArr,
      len = arr.length;

    for (var i = 0; i < len; i++) {
      //头部时
      if (i === len - 1) {
        //根据方向往下走一步
        this.setHeadXY(arr);
      } else {
        //第二项变成第三项，以此类推
        arr[i].x = arr[i + 1].x;
        arr[i].y = arr[i + 1].y;
      }
    }

    //先删除节点
    this.removeSnake();
    //重绘新的坐标
    this.initSnake();
    //身体碰撞机制
    this.headInBody(arr);
    //吃食物机制
    this.eatFood(arr);
  };

  //每次移动需要重绘(dom性能较低) 逐一删除，直至全部节点删除
  Snake.prototype.removeSnake = function () {
    var bodys = document.getElementsByClassName('round'),
      len = bodys.length;

    // while (len > 0) {
    //   bodys[0].remove();
    // }

    for (var i = len; i > 0; i--) {
      bodys[0].remove();
    }
  };

  Snake.prototype.changeDir = function (e) {
    var e = e || window.event,
      code = e.keyCode;

    // console.log(code);
    //settingA: (up: 38 down: 40 left: 37 right: 39)
    //settingB: (w: 87 s: 83 a: 65 d: 68)
    switch (this.keyboardSetting) {
      case 'A':
        this.setSettingADir(code);
        break;
      case 'B':
        this.setSettingBDir(code);
        break;
      default:
        break;
    }
  };

  //假如从上往下走时只能左右转弯 从左往右时只能上下转弯
  Snake.prototype.setSettingADir = function (code) {
    switch (code) {
      case 37: //left
        if (this.dir === 'DOWN' || this.dir === 'UP') {
          this.dir = 'LEFT';
        }
        break;
      case 39: //right
        if (this.dir === 'DOWN' || this.dir === 'UP') {
          this.dir = 'RIGHT';
        }
        break;
      case 38: //up
        if (this.dir === 'LEFT' || this.dir === 'RIGHT') {
          this.dir = 'UP';
        }
        break;
      case 40: //down
        if (this.dir === 'LEFT' || this.dir === 'RIGHT') {
          this.dir = 'DOWN';
        }
        break;
      default:
        break;
    }
  };

  Snake.prototype.setSettingBDir = function (code) {
    switch (code) {
      case 65: //left
        if (this.dir === 'DOWN' || this.dir === 'UP') {
          this.dir = 'LEFT';
        }
        break;
      case 68: //right
        if (this.dir === 'DOWN' || this.dir === 'UP') {
          this.dir = 'RIGHT';
        }
        break;
      case 87: //up
        if (this.dir === 'LEFT' || this.dir === 'RIGHT') {
          this.dir = 'UP';
        }
        break;
      case 83: //down
        if (this.dir === 'LEFT' || this.dir === 'RIGHT') {
          this.dir = 'DOWN';
        }
        break;
      default:
        break;
    }
  };

  //改变蛇头的位置，如果触碰边缘要瞬移至另外一边
  Snake.prototype.setHeadXY = function (arr) {
    var head = arr[arr.length - 1],
      { width, height } = getSnakeWrapWidthHeight.call(this);

    switch (this.dir) {
      //往左移动触碰边缘时
      case 'LEFT':
        if (head.x <= 0) {
          //瞬移后头部位置即是 盒子宽度 - 头部宽度
          head.x = width - 20;
        } else {
          head.x -= 20;
        }
        break;
      case 'RIGHT':
        if (head.x >= width - 20) {
          head.x = 0;
        } else {
          head.x += 20;
        }
        break;
      case 'UP':
        if (head.y <= 0) {
          head.y = height - 20;
        } else {
          head.y -= 20;
        }
        break;
      case 'DOWN':
        if (head.y >= height - 20) {
          head.y = 0;
        } else {
          head.y += 20;
        }
        break;
    }
  };

  //触碰身体会Game over
  Snake.prototype.headInBody = function (arr) {
    var headX = arr[arr.length - 1].x,
      headY = arr[arr.length - 1].y,
      item;

    //循环身体上的所有节点
    for (var i = 0; i < arr.length - 2; i++) {
      item = arr[i];

      if (headX === item.x && headY === item.y) {
        var _self = this;
        setTimeout(function () {
          _self.removeSnake();
          _self.removeFood();
          setStartBtnDisplay.call(_self, 'hide');
          displayGameResult.call(_self, 'show');
          console.log('game over');
          clearInterval(t);
          t = null;
        }, 100);
      }
    }
  };

  Snake.prototype.createFood = function () {
    var food = document.createElement('i'),
      oSnakeWrap = this.mountedEl.getElementsByClassName('snake-wrap')[0],
      { width, height } = getSnakeWrapWidthHeight.call(this);

    food.className = 'food';
    food.style.left = setRandomPos(width) * 20 + 'px';
    food.style.top = setRandomPos(height) * 20 + 'px';
    oSnakeWrap.appendChild(food);
  };

  //判断身子最后两个节点的坐标结果才能决定食物新增后存放的坐标位置
  Snake.prototype.eatFood = function (arr) {
    var food = document.getElementsByClassName('food')[0],
      foodX = parseInt(getStyles(food, 'left')),
      foodY = parseInt(getStyles(food, 'top')),
      headX = arr[arr.length - 1].x,
      headY = arr[arr.length - 1].y,
      x, //新增至身子末端的坐标
      y;

    //当蛇头坐标和食物坐标重合时说明吃中了食物
    if (headX === foodX && headY === foodY) {
      this.removeFood();
      this.createFood();

      //身子最后两位的x坐标相等时说明是蛇竖着走
      if (arr[0].x === arr[1].x) {
        x = arr[0].x;
        //往上走时
        if (arr[0].y > arr[1].y) {
          //添加食物到最末端
          y = arr[0].y + 20;
        } else if (arr[0].y < arr[1].y) {
          //往下走时
          y = arr[0].y - 20;
        }
      } else if (arr[0].y === arr[1].y) {
        //身子最后两位的y坐标相等时说明是蛇横着走
        y = arr[0].y;
        if (arr[0].x > arr[1].x) {
          //往左走时
          x = arr[0].x + 20;
        } else if (arr[0].x < arr[1].x) {
          x = arr[0].x - 20;
        }
      }

      //将x,y坐标的食物转移到第一项
      arr.unshift({ x, y });
    }
  };

  //吃了食物需要消失，并转移给身子的最末端
  Snake.prototype.removeFood = function () {
    var food = document.getElementsByClassName('food')[0];
    food && food.remove();
  };

  Snake.prototype.bindEvent = function ({ oStartBtn, oResetBtn }) {
    addEvent(oStartBtn, 'click', handleStartBtnClick.bind(this));
    addEvent(oResetBtn, 'click', handleResetBtnClick.bind(this));
    addEvent(document, 'keydown', handleKeyDown.bind(this));
  };

  function createSnakeElement() {
    return `
      <div class="snake">
        <div class="snake-wrap"></div>  
        <div class="btn-group">
          <button class="start">Begin</button>
          <button class="reset">Reset</button>
        </div>
        <div class="result">You lose!</div>
      </div>
    `;
  }

  function handleKeyDown() {
    this.changeDir();
  }

  function handleStartBtnClick(e) {
    var food = document.getElementsByClassName('food')[0];
    isBegin = !isBegin;
    isBegin ? btnSwitch.call(this, 'begin') : btnSwitch.call(this, 'pause');

    if (isBegin) {
      if (!food) {
        this.createFood();
      }

      this.run();
    } else {
      clearInterval(t);
      t = null;
    }
  }

  function handleResetBtnClick() {
    this.dir = 'DOWN';
    this.bodyArr = [
      { x: 0, y: 0 },
      { x: 0, y: 20 },
      { x: 0, y: 40 },
      { x: 0, y: 60 },
      { x: 0, y: 80 },
      { x: 0, y: 100 } //头
    ];
    btnSwitch.call(this, 'pause');
    setStartBtnDisplay.call(this, 'show');
    displayGameResult.call(this, 'hide');
    this.removeSnake();
    this.removeFood();
    this.createFood();
    this.initSnake();
    isBegin = false;
  }

  function setDifficulty(type) {
    var speed = 0;
    switch (type) {
      case 'easy':
        speed = 500;
        break;
      case 'medium':
        speed = 400;
        break;
      case 'hard':
        speed = 300;
        break;
      default:
        break;
    }
    return speed;
  }

  //边界内随机创建食物坐标 随机位置*20倍数=实际坐标
  function setRandomPos(widthOrHeight) {
    //widthOrHeight -> snakewrap width or height
    //Math.random() -> ( 0 - 1 )的小数
    //Math.floor(0.5087707998013575 * 500 / 20)
    return Math.floor(Math.random() * (widthOrHeight / 20));
  }

  function getSnakeWrapWidthHeight() {
    var oSnakeWrap = this.mountedEl.getElementsByClassName('snake-wrap')[0],
      snakeWrapWidth = parseInt(getStyles(oSnakeWrap, 'width')),
      snakeWrapHeight = parseInt(getStyles(oSnakeWrap, 'height'));

    return { width: snakeWrapWidth, height: snakeWrapHeight };
  }

  function btnSwitch(type) {
    var { oStartBtn } = this.getOElements();
    switch (type) {
      case 'begin':
        oStartBtn.innerText = 'Pause';
        break;
      case 'pause':
        oStartBtn.innerText = 'Begin';
        break;
      default:
        break;
    }
  }

  function setStartBtnDisplay(type) {
    var { oStartBtn } = this.getOElements();
    switch (type) {
      case 'show':
        oStartBtn.style.display = 'inline-block';
        break;
      case 'hide':
        oStartBtn.style.display = 'none';
        break;
      default:
        break;
    }
  }

  function displayGameResult(type) {
    var { oResult } = this.getOElements();
    switch (type) {
      case 'show':
        oResult.style.display = 'block';
        break;
      case 'hide':
        oResult.style.display = 'none';
        break;
      default:
        break;
    }
  }

  window.Snake = Snake;
})();
